#include "CentralCache.h"
#include "PageCache.h"

// 初始化单例实例
CentralCache CentralCache::_sInst;

/**
 * 从中心缓存获取一个非空的span
 * @param list 对应大小的span链表
 * @param size 需要的内存块大小
 * @return 返回可用的span
 */
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
    // 先在当前span链表中查找是否有空闲内存块的span
    Span* it = list.Begin();
    while (it != list.End())
    {
        if (it->_freeList != nullptr)
        {
            return it;
        }
        else
        {
            it = it->_next;
        }
    }

    // 解锁当前桶锁，避免在向page cache申请时持有锁导致其他线程无法释放内存
    list._mtx.unlock();

    // 当前链表没有可用span，需要向page cache申请
    PageCache::GetInstance()->_pageMtx.lock();
    Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));
    span->_isUse = true;
    span->_objSize = size;
    PageCache::GetInstance()->_pageMtx.unlock();

    // 对获取的span进行切分，这里不需要加锁因为其他线程无法访问这个新span
    
    // 计算span的起始地址和大小
    char* start = (char*)(span->_pageId << PAGE_SHIFT);  // 页号转换为地址
    size_t bytes = span->_n << PAGE_SHIFT;               // 页数转换为字节数
    char* end = start + bytes;                          // 结束地址

    // 将大块内存切分成小块并连接成自由链表
    span->_freeList = start;  // 第一块作为链表头
    start += size;            // 移动到下一块
    void* tail = span->_freeList;  // 链表尾指针
    
    int i = 1;  // 计数，从1开始因为已经处理了第一块
    while (start + size < end)  // 确保不会越界
    {
        ++i;
        NextObj(tail) = start;  // 将当前块连接到链表
        tail = NextObj(tail);   // 移动尾指针
        start += size;          // 移动到下一块
    }

    NextObj(tail) = nullptr;  // 链表末尾置空

    // 将切分好的span插入到对应桶中，此时需要重新加锁
    list._mtx.lock();
    list.PushFront(span);

    return span;
}

/**
 * 从中心缓存获取一定数量的内存对象
 * @param start [输出]获取到的内存块起始地址
 * @param end [输出]获取到的内存块结束地址
 * @param batchNum 请求获取的数量
 * @param size 每个内存块的大小
 * @return 实际获取到的数量
 */
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
    size_t index = SizeClass::Index(size);  // 计算对应的桶索引
    _spanLists[index]._mtx.lock();         // 加桶锁

    Span* span = GetOneSpan(_spanLists[index], size);  // 获取可用span
    assert(span);
    assert(span->_freeList);  // span必须有空闲内存块

    // 从span中获取batchNum个对象
    start = span->_freeList;  // 链表头作为起始
    end = start;              // 初始化结束指针
    size_t i = 0;
    size_t actualNum = 1;     // 实际获取数量，至少1个
    
    // 遍历链表获取指定数量的内存块
    while (i < batchNum - 1 && NextObj(end) != nullptr)
    {
        end = NextObj(end);  // 移动结束指针
        ++i;
        ++actualNum;
    }
    
    // 更新span的自由链表和计数器
    span->_freeList = NextObj(end);  // 剩余的内存块
    NextObj(end) = nullptr;          // 截断链表
    span->_useCount += actualNum;    // 增加使用计数

    _spanLists[index]._mtx.unlock();  // 释放桶锁

    return actualNum;
}

/**
 * 将一批内存块释放回对应的span
 * @param start 内存块链表的起始地址
 * @param size 每个内存块的大小
 */
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
    size_t index = SizeClass::Index(size);  // 计算对应的桶索引
    _spanLists[index]._mtx.lock();         // 加桶锁

    while (start)
    {
        void* next = NextObj(start);  // 保存下一个内存块地址

        // 找到内存块所属的span
        Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
        
        // 将内存块插回span的自由链表(头插法)
        NextObj(start) = span->_freeList;
        span->_freeList = start;
        span->_useCount--;  // 减少使用计数

        // 如果span中所有内存块都已归还
        if (span->_useCount == 0)
        {
            _spanLists[index].Erase(span);  // 从链表中移除
            span->_freeList = nullptr;      // 清空自由链表
            span->_next = nullptr;          // 清空链表指针
            span->_prev = nullptr;

            // 临时释放桶锁，避免死锁
            _spanLists[index]._mtx.unlock();

            // 将空span归还给page cache
            PageCache::GetInstance()->_pageMtx.lock();
            PageCache::GetInstance()->ReleaseSpanToPageCache(span);
            PageCache::GetInstance()->_pageMtx.unlock();

            // 重新获取桶锁继续处理剩余内存块
            _spanLists[index]._mtx.lock();
        }

        start = next;  // 处理下一个内存块
    }

    _spanLists[index]._mtx.unlock();  // 释放桶锁
}